package com.zaicheng.cloud.remotecall;

import com.zaicheng.cloud.netty.RpcRequest;
import com.zaicheng.cloud.remotecall.template.*;

import java.io.Serializable;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public final class RequestTemplate implements Serializable {
    private static final Pattern QUERY_STRING_PATTERN = Pattern.compile("(?<!\\{)\\?");
    private final Map<String, QueryTemplate> queries = new LinkedHashMap();
    private final Map<String, HeaderTemplate> headers;
    private String target;
    private String fragment;
    private boolean resolved;
    private UriTemplate uriTemplate;
    private BodyTemplate bodyTemplate;
    private RpcRequest.HttpMethod method;
    private transient Charset charset;
    private RpcRequest.Body body;
    private boolean decodeSlash;
    private CollectionFormat collectionFormat;
    private MethodMetadata methodMetadata;
    private Target<?> remoteCallTarget;


    public  RequestTemplate(){
        this.headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        this.resolved = false;
        this.charset = Util.UTF_8;
        this.body = RpcRequest.Body.empty();
    }

    public RequestTemplate(Map<String, HeaderTemplate> headers) {
        this.headers = new TreeMap(String.CASE_INSENSITIVE_ORDER);
        this.resolved = false;
        this.charset = Util.UTF_8;
        this.body = RpcRequest.Body.empty();
        this.decodeSlash = true;
       // this.collectionFormat = CollectionFormat.EXPLODED;
    }

    private RequestTemplate(String target, String fragment, Charset charset, RpcRequest.Body body, boolean decodeSlash,  MethodMetadata methodMetadata, Target<?> remoteCallTarget) {
        this.headers = new TreeMap(String.CASE_INSENSITIVE_ORDER);
        this.resolved = false;
        this.charset = Util.UTF_8;
        this.body = RpcRequest.Body.empty();
        this.decodeSlash = true;
        this.collectionFormat = CollectionFormat.EXPLODED;
        this.target = target;
        this.fragment = fragment;
//        this.method = method;
        this.charset = charset;
        this.body = body;
        this.decodeSlash = decodeSlash;
//        this.collectionFormat = collectionFormat != null ? collectionFormat : CollectionFormat.EXPLODED;
        this.methodMetadata = methodMetadata;
        this.remoteCallTarget = remoteCallTarget;
    }

    public RequestTemplate(String target, String fragment, UriTemplate uriTemplate, BodyTemplate bodyTemplate, Charset charset, RpcRequest.Body body, boolean decodeSlash, CollectionFormat collectionFormat, MethodMetadata methodMetadata, Target<?> remoteCallTarget, Map<String, HeaderTemplate> headers) {
        this.headers = headers;
    }

    public static RequestTemplate from(RequestTemplate requestTemplate) {
        RequestTemplate template = new RequestTemplate(requestTemplate.target, requestTemplate.fragment, requestTemplate.uriTemplate, requestTemplate.bodyTemplate, requestTemplate.charset, requestTemplate.body, requestTemplate.decodeSlash, requestTemplate.collectionFormat, requestTemplate.methodMetadata, requestTemplate.remoteCallTarget,requestTemplate.headers);
        if (!requestTemplate.queries().isEmpty()) {
            template.queries.putAll(requestTemplate.queries);
        }

        if (!requestTemplate.headers().isEmpty()) {
            template.headers.putAll(requestTemplate.headers);
        }

        return template;
    }

    /** @deprecated */
    @Deprecated
    public RequestTemplate(RequestTemplate toCopy) {
        this.headers = new TreeMap(String.CASE_INSENSITIVE_ORDER);
        this.resolved = false;
        this.charset = Util.UTF_8;
        this.body = RpcRequest.Body.empty();
        this.decodeSlash = true;
        this.collectionFormat = CollectionFormat.EXPLODED;
        Util.checkNotNull(toCopy, "toCopy", new Object[0]);
        this.target = toCopy.target;
        this.fragment = toCopy.fragment;
//        this.method = toCopy.method;
//        this.queries.putAll(toCopy.queries);
//        this.headers.putAll(toCopy.headers);
        this.charset = toCopy.charset;
        this.body = toCopy.body;
        this.decodeSlash = toCopy.decodeSlash;
//        this.collectionFormat = toCopy.collectionFormat != null ? toCopy.collectionFormat : CollectionFormat.EXPLODED;
//        this.uriTemplate = toCopy.uriTemplate;
//        this.bodyTemplate = toCopy.bodyTemplate;
        this.resolved = false;
        this.methodMetadata = toCopy.methodMetadata;
        this.target = toCopy.target;
        this.remoteCallTarget = toCopy.remoteCallTarget;
    }

    public RequestTemplate resolve(Map<String, ?> variables) {
        StringBuilder uri = new StringBuilder();
        RequestTemplate resolved = from(this);
        if (this.uriTemplate == null) {
            this.uriTemplate = UriTemplate.create("", !this.decodeSlash, this.charset);
        }

        String expanded = this.uriTemplate.expand(variables);
        if (expanded != null) {
            uri.append(expanded);
        }

        String headerValues;
        String queryString;
        if (!this.queries.isEmpty()) {
            resolved.queries(Collections.emptyMap());
            StringBuilder query = new StringBuilder();
            Iterator queryTemplates = this.queries.values().iterator();

            while(queryTemplates.hasNext()) {
                QueryTemplate queryTemplate = (QueryTemplate)queryTemplates.next();
                headerValues = queryTemplate.expand(variables);
                if (Util.isNotBlank(headerValues)) {
                    query.append(headerValues);
                    if (queryTemplates.hasNext()) {
                        query.append("&");
                    }
                }
            }

            queryString = query.toString();
            if (!queryString.isEmpty()) {
                Matcher queryMatcher = QUERY_STRING_PATTERN.matcher(uri);
                if (queryMatcher.find()) {
                    uri.append("&");
                } else {
                    uri.append("?");
                }

                uri.append(queryString);
            }
        }

        resolved.uri(uri.toString());
//        if (!this.headers.isEmpty()) {
//            resolved.headers(Collections.emptyMap());
//            Iterator var9 = this.headers.values().iterator();
//
//            while(var9.hasNext()) {
//                HeaderTemplate headerTemplate = (HeaderTemplate)var9.next();
//                queryString = headerTemplate.expand(variables);
//                if (!queryString.isEmpty()) {
//                    headerValues = queryString.substring(queryString.indexOf(" ") + 1);
//                    if (!headerValues.isEmpty()) {
//                        resolved.header(headerTemplate.getName(), Literal.create(headerValues));
//                    }
//                }
//            }
//        }

        if (this.bodyTemplate != null) {
            resolved.body(this.bodyTemplate.expand(variables));
        }

        resolved.resolved = true;
        return resolved;
    }

    /** @deprecated */
    @Deprecated
    RequestTemplate resolve(Map<String, ?> unencoded, Map<String, Boolean> alreadyEncoded) {
        return this.resolve(unencoded);
    }

    public RpcRequest request() {
        if (!this.resolved) {
            throw new IllegalStateException("template has not been resolved.");
        } else {
            return RpcRequest.create(this.url(),this.body, this);
        }
    }

//    /** @deprecated */
//    @Deprecated
//    public RequestTemplate method(String method) {
//        Util.checkNotNull(method, "method", new Object[0]);
//
//        try {
//            this.method = HttpMethod.valueOf(method);
//            return this;
//        } catch (IllegalArgumentException var3) {
//            throw new IllegalArgumentException("Invalid HTTP Method: " + method);
//        }
//    }

    public RequestTemplate method(RpcRequest.HttpMethod method) {
        Util.checkNotNull(method, "method", new Object[0]);
        this.method = method;
        return this;
    }

    public String method() {
        return this.method != null ? this.method.name() : null;
    }

    public RequestTemplate decodeSlash(boolean decodeSlash) {
        this.decodeSlash = decodeSlash;
        this.uriTemplate = UriTemplate.create(this.uriTemplate.toString(), !this.decodeSlash, this.charset);
        if (!this.queries.isEmpty()) {
            this.queries.replaceAll((key, queryTemplate) -> {
                return QueryTemplate.create(queryTemplate.getName(), queryTemplate.getValues(), this.charset,decodeSlash);
            });
        }

        return this;
    }

    public boolean decodeSlash() {
        return this.decodeSlash;
    }



    public RequestTemplate uri(String uri) {
        return this.uri(uri, false);
    }

    public RequestTemplate uri(String uri, boolean append) {
        if (UriUtils.isAbsolute(uri)) {
            throw new IllegalArgumentException("url values must be not be absolute.");
        } else {
            if (uri == null) {
                uri = "/";
            } else if (!uri.isEmpty() && !uri.startsWith("/") && !uri.startsWith("{") && !uri.startsWith("?") && !uri.startsWith(";")) {
                uri = "/" + uri;
            }

            Matcher queryMatcher = QUERY_STRING_PATTERN.matcher(uri);
            if (queryMatcher.find()) {
                String queryString = uri.substring(queryMatcher.start() + 1);
                this.extractQueryTemplates(queryString, append);
                uri = uri.substring(0, queryMatcher.start());
            }

            int fragmentIndex = uri.indexOf(35);
            if (fragmentIndex > -1) {
                this.fragment = uri.substring(fragmentIndex);
                uri = uri.substring(0, fragmentIndex);
            }

            if (append && this.uriTemplate != null) {
                this.uriTemplate = UriTemplate.append(this.uriTemplate, uri);
            } else {
                this.uriTemplate = UriTemplate.create(uri, !this.decodeSlash, this.charset);
            }

            return this;
        }
    }

    public RequestTemplate target(String target) {
        if (Util.isBlank(target)) {
            return this;
        } else if (!UriUtils.isAbsolute(target)) {
            throw new IllegalArgumentException("target values must be absolute.");
        } else {
            if (target.endsWith("/")) {
                target = target.substring(0, target.length() - 1);
            }

            try {
                URI targetUri = URI.create(target);
                if (Util.isNotBlank(targetUri.getRawQuery())) {
                    this.extractQueryTemplates(targetUri.getRawQuery(), true);
                }

                this.target = targetUri.getScheme() + "://" + targetUri.getAuthority() + targetUri.getPath();
                if (targetUri.getFragment() != null) {
                    this.fragment = "#" + targetUri.getFragment();
                }

                return this;
            } catch (IllegalArgumentException var3) {
                throw new IllegalArgumentException("Target is not a valid URI.", var3);
            }
        }
    }

    public String url() {
        StringBuilder url = new StringBuilder(this.path());
        if (!this.queries.isEmpty()) {
            url.append(this.queryLine());
        }

        if (this.fragment != null) {
            url.append(this.fragment);
        }

        return url.toString();
    }

    public String path() {
        StringBuilder path = new StringBuilder();
        if (this.target != null) {
            path.append(this.target);
        }
        if (this.uriTemplate != null) {
            path.append(this.uriTemplate.toString());
        }
        if (path.length() == 0) {
            path.append("/");
        }
        return path.toString();
    }

    public List<String> variables() {
        List<String> variables = new ArrayList(this.uriTemplate.getVariables());
        Iterator var2 = this.queries.values().iterator();

        while(var2.hasNext()) {
            QueryTemplate queryTemplate = (QueryTemplate)var2.next();
            variables.addAll(queryTemplate.getVariables());
        }

//        var2 = this.headers.values().iterator();

//        while(var2.hasNext()) {
//            HeaderTemplate headerTemplate = (HeaderTemplate)var2.next();
//            variables.addAll(headerTemplate.getVariables());
//        }

        if (this.bodyTemplate != null) {
            variables.addAll(this.bodyTemplate.getVariables());
        }

        return variables;
    }

    public RequestTemplate query(String name, String... values) {
        return values == null ? this.query(name, (Iterable)Collections.emptyList()) : this.query(name, (Iterable)Arrays.asList(values));
    }

    public RequestTemplate query(String name, Iterable<String> values) {
        return this.appendQuery(name, values, this.collectionFormat);
    }

    public RequestTemplate query(String name, Iterable<String> values, CollectionFormat collectionFormat) {
        return this.appendQuery(name, values, collectionFormat);
    }

    private RequestTemplate appendQuery(String name, Iterable<String> values, CollectionFormat collectionFormat) {
        if (!values.iterator().hasNext()) {
            this.queries.remove(name);
            return this;
        } else {
            this.queries.compute(name, (key, queryTemplate) -> {
                return queryTemplate == null ? QueryTemplate.create(name, values, this.charset,this.decodeSlash) : QueryTemplate.append(queryTemplate, values, this.decodeSlash);
            });
            return this;
        }
    }

    public RequestTemplate queries(Map<String, Collection<String>> queries) {
        if (queries != null && !queries.isEmpty()) {
            queries.forEach(this::query);
        } else {
            this.queries.clear();
        }

        return this;
    }

    public Map<String, Collection<String>> queries() {
        Map<String, Collection<String>> queryMap = new LinkedHashMap();
        this.queries.forEach((key, queryTemplate) -> {
            List<String> values = new ArrayList(queryTemplate.getValues());
            queryMap.put(key, Collections.unmodifiableList(values));
        });
        return Collections.unmodifiableMap(queryMap);
    }

    public RequestTemplate header(String name, String... values) {
        return this.header(name, (Iterable)Arrays.asList(values));
    }

    private RequestTemplate header(String name, TemplateChunk... chunks) {
        if (chunks == null) {
            throw new IllegalArgumentException("chunks are required.");
        } else {
            return this.appendHeader(name, Arrays.asList(chunks));
        }
    }

    public RequestTemplate header(String name, Iterable<String> values) {
        if (name != null && !name.isEmpty()) {
            if (values == null) {
                values = Collections.emptyList();
            }

            return this.appendHeader(name, (Iterable)values);
        } else {
            throw new IllegalArgumentException("name is required.");
        }
    }

    public RequestTemplate removeHeader(String name) {
        if (name != null && !name.isEmpty()) {
            this.headers.remove(name);
            return this;
        } else {
            throw new IllegalArgumentException("name is required.");
        }
    }

    private RequestTemplate appendHeader(String name, Iterable<String> values) {
        if (!values.iterator().hasNext()) {
            this.headers.remove(name);
            return this;
        } else {
            this.headers.compute(name, (headerName, headerTemplate) -> {
                return headerTemplate == null ? HeaderTemplate.create(headerName, values) : HeaderTemplate.append(headerTemplate, values);
            });
            return this;
        }
    }

    private RequestTemplate appendHeader(String name, List<TemplateChunk> chunks) {
        if (chunks.isEmpty()) {
            this.headers.remove(name);
            return this;
        } else {
            this.headers.compute(name, (headerName, headerTemplate) -> {
                return headerTemplate == null ? HeaderTemplate.from(name, chunks) : HeaderTemplate.appendFrom(headerTemplate, chunks);
            });
            return this;
        }
    }

    public RequestTemplate headers(Map<String, Collection<String>> headers) {
        if (headers != null && !headers.isEmpty()) {
            headers.forEach(this::header);
        } else {
            this.headers.clear();
        }

        return this;
    }

    public Map<String, Collection<String>> headers() {
        Map<String, Collection<String>> headerMap = new TreeMap(String.CASE_INSENSITIVE_ORDER);
        this.headers.forEach((key, headerTemplate) -> {
            List<String> values = new ArrayList(headerTemplate.getValues());
            if (!values.isEmpty()) {
                headerMap.put(key, Collections.unmodifiableList(values));
            }

        });
        return Collections.unmodifiableMap(headerMap);
    }

    public RequestTemplate body(byte[] data, Charset charset) {
        this.body(RpcRequest.Body.create(data, charset));
        return this;
    }

    public RequestTemplate body(String bodyText) {
        this.body(RpcRequest.Body.create(bodyText.getBytes(this.charset), this.charset));
        return this;
    }

    /** @deprecated */
    @Deprecated
    public RequestTemplate body(RpcRequest.Body body) {
        this.body = body;
        this.bodyTemplate = null;
//        this.header("Content-Length", (Iterable)Collections.emptyList());
//        if (body.length() > 0) {
//            this.header("Content-Length", String.valueOf(body.length()));
//        }

        return this;
    }

    public Charset requestCharset() {
        return this.body != null ? (Charset)this.body.getEncoding().orElse(this.charset) : this.charset;
    }

    public byte[] body() {
        return this.body.asBytes();
    }

    /** @deprecated */
    @Deprecated
    public RpcRequest.Body requestBody() {
        return this.body;
    }

    public RequestTemplate bodyTemplate(String bodyTemplate) {
        this.bodyTemplate = BodyTemplate.create(bodyTemplate, this.charset);
        return this;
    }

    public RequestTemplate bodyTemplate(String bodyTemplate, Charset charset) {
        this.bodyTemplate = BodyTemplate.create(bodyTemplate, charset);
        this.charset = charset;
        return this;
    }

    public String bodyTemplate() {
        return this.bodyTemplate != null ? this.bodyTemplate.toString() : null;
    }

    @Override
    public String toString() {
        return this.request().toString();
    }

    public boolean hasRequestVariable(String variable) {
        return this.getRequestVariables().contains(variable);
    }

    public Collection<String> getRequestVariables() {
        Collection<String> variables = new LinkedHashSet(this.uriTemplate.getVariables());
        this.queries.values().forEach((queryTemplate) -> {
            variables.addAll(queryTemplate.getVariables());
        });
//        this.headers.values().forEach((headerTemplate) -> {
//            variables.addAll(headerTemplate.getVariables());
//        });
        return variables;
    }

    public boolean resolved() {
        return this.resolved;
    }

    public String queryLine() {
        StringBuilder queryString = new StringBuilder();
        if (!this.queries.isEmpty()) {
            Iterator iterator = this.queries.values().iterator();

            while(iterator.hasNext()) {
                QueryTemplate queryTemplate = (QueryTemplate)iterator.next();
                String query = queryTemplate.toString();
                if (query != null && !query.isEmpty()) {
                    queryString.append(query);
                    if (iterator.hasNext()) {
                        queryString.append("&");
                    }
                }
            }
        }
        String result = queryString.toString();
        if (result.endsWith("&")) {
            result = result.substring(0, result.length() - 1);
        }
        if (!result.isEmpty()) {
            result = "?" + result;
        }
        return result;
    }

    private void extractQueryTemplates(String queryString, boolean append) {
        Map<String, List<String>> queryParameters = (Map) Arrays.stream(queryString.split("&")).map(this::splitQueryParameter).collect(Collectors.groupingBy(AbstractMap.SimpleImmutableEntry::getKey, LinkedHashMap::new, Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
        if (!append) {
            this.queries.clear();
        }
        queryParameters.forEach(this::query);
    }

    private AbstractMap.SimpleImmutableEntry<String, String> splitQueryParameter(String pair) {
        int eq = pair.indexOf("=");
        String name = eq > 0 ? pair.substring(0, eq) : pair;
        String value = eq > 0 && eq < pair.length() ? pair.substring(eq + 1) : null;
        return new AbstractMap.SimpleImmutableEntry(name, value);
    }

    @Experimental
    public RequestTemplate methodMetadata(MethodMetadata methodMetadata) {
        this.methodMetadata = methodMetadata;
        return this;
    }

    @Experimental
    public RequestTemplate remoteCallTarget(Target<?> remoteCallTarget) {
        this.remoteCallTarget = remoteCallTarget;
        return this;
    }

    @Experimental
    public MethodMetadata methodMetadata() {
        return this.methodMetadata;
    }

    @Experimental
    public Target<?> remoteCallTarget() {
        return this.remoteCallTarget;
    }

    interface Factory {
        RequestTemplate create(Object[] var1);
    }
}
